package learn.spring.init.core;

import learn.spring.init.annotation.Autowired;
import learn.spring.init.annotation.Component;
import learn.spring.init.annotation.ComponentScan;
import learn.spring.init.annotation.Scope;
import learn.spring.init.aware.BeanNameAware;
import learn.spring.init.init.InitializingBean;
import learn.spring.init.postprocessor.BeanPostProcessor;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ShenHao
 */
public class ApplicationContext {

    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public ApplicationContext(Class configClass) {
        List<Class> classList = scan(configClass);

        // 扫描路径下的类，生成BeanDefinition
        for (Class clazz : classList) {
            if (clazz.isAnnotationPresent(Component.class)) {
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setBeanClass(clazz);

                Component component = (Component) clazz.getAnnotation(Component.class);

                String beanName = component.value();
//                System.out.println("beanName = " + beanName);

                if (clazz.isAnnotationPresent(Scope.class)) {
                    Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                    beanDefinition.setScope(scope.value());
                } else {
                    beanDefinition.setScope("singleton");
                }

                // BeanPostProcessor
                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                    try {
                        BeanPostProcessor o = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
                        beanPostProcessorList.add(o);
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }

                beanDefinitionMap.put(beanName, beanDefinition);
            }
        }
        System.out.println("扫描包，生成BeanDefinition：");
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            System.out.println(beanDefinition);
        }
        System.out.println("\n");

        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScope().equals("singleton")) {

                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        System.out.println("beanName = " + beanName);
        Class beanClass = beanDefinition.getBeanClass();
        try {
            // 实例化
            System.out.println("实例化");
            Object bean = beanClass.getDeclaredConstructor().newInstance();

            // 填充属性
            System.out.println("填充属性");
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Object bean1 = getBean(field.getName());
                    field.setAccessible(true);
                    field.set(bean, bean1);
                }
            }

            // Aware
            System.out.println("Aware");
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }

            // BeanPostProcessor - before
            System.out.println("BeanPostProcessor - before");
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessBeforeInitialization(bean, beanName);
            }

            // 初始化
            System.out.println("InitializingBean");
            if (bean instanceof InitializingBean) {
                ((InitializingBean) bean).afterPropertiesSet();
            }

            // BeanPostProcessor - after
            System.out.println("BeanPostProcessor - after");
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessAfterInitialization(bean, beanName);
            }
            System.out.println();

            return bean;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        return null;
    }

    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("prototype")) {
            return createBean(beanName, beanDefinition);
        } else {
            Object o = singletonObjects.get(beanName);
            if (o == null) {
                o = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, o);
            }
            return o;
        }
    }

    private List<Class> scan(Class configClass) {
        List<Class> classList = new ArrayList<>();

        // 判断是否有ComponentScan注解
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String scanPath = componentScan.value();
//            System.out.println("scanPath = " + scanPath);

            scanPath = scanPath.replace(".", "/");

//            ClassLoader classLoader = this.getClass().getClassLoader();
            ClassLoader classLoader = ApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(scanPath);

            File dir = new File(resource.getFile());

            File[] files = dir.listFiles();
            for (File file : files) {
                String absolutePath = file.getAbsolutePath();
                absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.lastIndexOf(".class"));
                absolutePath = absolutePath.replace("/", ".");
//                System.out.println("absolutePath = " + absolutePath);

                try {
                    Class<?> clazz = classLoader.loadClass(absolutePath);

                    classList.add(clazz);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        return classList;
    }

    private void getClassList(List<Class> classList, ClassLoader classLoader, File[] files) {
        for (File file : files) {
            if (!file.toString().endsWith(".class") && file.listFiles().length > 0) {
                for (File file2 : file.listFiles()) {
                    getClassList(classList, classLoader, file2.listFiles());
                }
            } else {
                String absolutePath = file.getAbsolutePath();
                absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.lastIndexOf(".class"));
                absolutePath = absolutePath.replace("/", ".");
//                System.out.println("absolutePath = " + absolutePath);

                try {
                    Class<?> clazz = classLoader.loadClass(absolutePath);

                    classList.add(clazz);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
